/**
 * 验证码自动填充 : 
 * 
 * 有两种情况
 * 1. Html 中存在验证码本码, 直接提取即可;
 * 2. Html 中是验证码图片, 提取图片进行 OCR 识别;
 * 
 * 
 * 大致逻辑 :
 *   页面载入时, 判断该页面是 "验证码本码" 还是 "验证码图片"
 *   1. "验证码本码"
 *        1. 监听验证码的 div 的文本变化   
 *        2. 文本变化时, 直接提取验证码
 *        3. 提取到验证码后写入到输入框
 *   2. "验证码图片"
 *        1. 监听验证码的 div 的子节点变化 ( 图片 div 的上一层 div )
 *        2. 子节点变化时, 提取图片 div 下验证码图片的 src 属性
 *        3. 使用 src 的 base64 部分, 调用 OCR 获取验证码文本
 *        4. 将 OCR 识别到的验证码文本写入到输入框
 * 
 * 
 * 注意点 : 
 *    1. 因为目标页面是 https 的, 因此不能使用 http 的 OCR 服务, 浏览器不支持将 http 接口的数据填充到 https 的页面 ... 
 *    2. 由于 https 需要 ssl 证书, 安装插件后请点击插件的 "take crt" 按钮下载并安装证书, 下载链接为局域网连接 ...
 *    3. 监听变化时, 两种页面的监听方式不通过
 *        3.1. 验证码本码, 监听本码的 div 的文本变化即可;
 *        3.2. 验证码图片, 点击时是生成新的 img 标签的, 因此需要监听 img 父层 div 的子节点变化;
 *        3.3. 我是区分开来写了两个方法, 其实写一个方法也行, 在方法内部做判断即可, 先判断子元素变化, 再判断文本变化;
 */


// OCR_URL :
const OCR_URL = 'https://192.168.0.118:5010/api/py/ocr64'
const VERIFY_CODE_LEN = 4

// 页面加载时, 提取验证码并回填 : ( 区分 html 中直接包含验证码和 html 中仅有验证码图片两种情况 )
window.addEventListener('load', () => {
  /**
   * 页面加载时, 判断是 [运营端/全病程/CRM] 还是 [GCP/EDC]
   * [运营端/全病程/CRM], 则直接从 Html 中获取验证码数据并填充
   * [GCP/EDC], 则提取验证码图片的 src, 调用 OCR 接口, 填充 OCR 识别结果
   */
  console.log('%c🌟 YL Verify Code Auto Input 🌟',
    'background: #00998F; color: #bada55; font-size: 20px; padding: 10px;');
  console.log(`
%c ------------------------------------------------------------
|   GGGG  |  IIIII  |  N   N  |   GGGG  |  EEEEE  |  RRRRR   |
|  G      |    I    |  NN  N  |  G      |  E      |  R    R  |
|  G  GG  |    I    |  N N N  |  G  GG  |  EEEE   |  RRRRR   |
|  G   G  |    I    |  N  NN  |  G   G  |  E      |  R  R    |
|   GGGG  |  IIIII  |  N   N  |   GGGG  |  EEEEE  |  R   R   |
 ------------------------------------------------------------`,
    'color: #00998F; font-size: 10px;');

  // GCP/EDC :
  if (is_verify_as_img()) {
    console.log('===>>访问页面 : GCP / EDC, 需要 OCR 识别后填充');
    // 提取验证码图片, 调用 OCR, 写入 OCR 识别结果 :
    observe_img_verify_code();
  } else {
    console.log('===>>访问页面 : 运营端 / 全病程 / CRM, 直接 Html 提取后填充');
    // 从 Html 源码获取验证码 :
    observe_txt_verify_code();
  }
});


// 验证码是否为图片 : ( GCP / EDC )
function is_verify_as_img() {
  /**
   * 分类 :
   * 1. 运营端, 全病程, CRM
   * 2. GCP, EDC
   * 
   * 说明 :
   * 1 --> Html 中有验证码本码;
   * 2 --> Html 中是验证码图片;
   * 
   * 返回 : 
   *  1 : False
   *  2 : True 
   */

  // 验证码为图片的网站登录页链接 :
  const verify_img_url_s = [
    "https://gcp-beta.yljk.cn/login",
    "https://gcp-test.yljk.cn/login",
    "https://gcp-pre.yljk.cn/login",
    "https://gcp.yljk.cn/login",

    "https://ky-beta.yljk.cn/login",
    "https://ky-test.yljk.cn/login",
    "https://ky-pre.yljk.cn/login",
    "https://ky.yljk.cn/login"
  ];

  // 检查当前页面 URL 是否在允许的列表中 :
  return verify_img_url_s.includes(window.location.href);
}


// 向输入框写入验证码 :
function input_verify_code(ele_css_selector, verify_code_value) {
  /**
   * 根据 css_selector 查找输入框并写入 :
   * 
   * 各端的登录页, 输入框 id 均为 'verifyCode', css_selector 则为 : '#verifyCode'
   */
  const verify_code_input = document.querySelector(ele_css_selector);
  if (verify_code_value) {
    // 自动填充验证码
    verify_code_input.value = verify_code_value;
    // 创建并触发一个 'input' 事件，模拟用户输入行为
    const input_event = new Event('input', { bubbles: true });
    verify_code_input.dispatchEvent(input_event);

    // 如果需要触发 'blur' 事件，模拟用户离开输入框
    const blur_event = new Event('blur', { bubbles: true });
    verify_code_input.dispatchEvent(blur_event);
    console.log('验证码回填成功 : ' + verify_code_value);
  } else {
    console.error('验证码输入框未找到, 验证码回填失败!');
  }
}


// 获取验证码的图片的 src :
function get_verify_code_img_src() {
  const verify_code_box = document.querySelector('.verifyCode_box');
  if (verify_code_box) {
    const img = verify_code_box.querySelector('img');
    if (img) {
      // console.log(img.src)
      const src = img.src;
      // 去掉 base64 数据前缀部分 ( data:image/png;base64, )
      const base_64_data = src.replace('data:image/png;base64,', '');
      return base_64_data;
    }
  }
  console.error('未获取到图片!')
  return null;
}


// OCR 识别 :
function fetch_ocr(img_src) {
  /**
   * OCR 识别部署在 192.168.0.118 服务器, 这是一个局域网内网服务器, 外网无法访问, 
   * 
   * OCR 识别的返回结果, 是数组嵌套数组,
   * 由于验证码图片是有背景的, OCR 识别时, 
   * 可能会误将背景识别成字符, ( ' ' / ',' / ':' / '·' / '-' / '_' / '=' / "'" / '`' / '.' / '~' 等 )
   * 故需对这些字符做替换, 替换为空串
   */
  if (img_src) {
    console.info('开始调用 OCR 识别');
    fetch(OCR_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', },
      body: JSON.stringify({ image: img_src })
    })
      .then(response => response.json())
      .then(data => {
        // console.log('OCR 接口返回数据: ' + JSON.stringify(data));
        // console.log('OCR 接口返回数据: \n' + JSON.stringify(data));
        console.debug('OCR 接口返回数据: \n' + JSON.stringify(data, null, 2));
        const verify_code = data.result[0][0][0];
        const verify_value = verify_code
          .replace(/\s+/g, '')
          .replace(',', '')
          .replace(':', '')
          .replace('·', '')
          .replace('-', '')
          .replace('=', '')
          .replace('_', '')
          .replace('~', '')
          .replace(/\./g, '')
          .replace("'", '')
          .replace('≤', '')
          .replace('↓', '')
          .replace('×', 'x')
          .replace('」', 'J')
          .replace(']', 'J')
          .replace('!', 'I')
          .replace('μ', 'v')  // u 一般不会识别为 μ, 故改为 v
          .replace('下', 'T');
        console.log('验证码识别结果 : ' + verify_code);
        console.log('验证码使用结果 : ' + verify_value);
        input_verify_code('#verifyCode', verify_value);
      })
      .catch(error => {
        console.error('接口调用失败!' + error);
      });
  } else {
    console.error('验证码图片未找到 !');
  }
}


// 从 html 源码中提取验证码 :
function observe_txt_verify_code() {
  /**
   * 从 html 源码中提取验证码并填充
   * 当验证码被点击刷新时, 验证码 div[.verifyCode_box] 的文本值会发生变化
   * 因此, 要让点击刷新验证码时, 回填新的验证码信息, 就需要监听 .verifyCode_box 的 characterData 变化
   */
  const verify_code_div = document.querySelector('.verifyCode_box');
  if (verify_code_div) {
    console.debug('验证码 div 找到');

    let txt_observer;

    // 观察验证码 div 的文本变化 :
    function start_txt_observing() {
      if (!txt_observer) {
        console.debug('创建观察验证码文本变化方法')
        txt_observer = new MutationObserver((mutationsList) => {
          mutationsList.forEach(mutation => {
            if (mutation.type === 'characterData') handle_txt_change();
          });
        });

        // 获取 .verifyCode_box 中的文本节点, 并开始观察文本属性变化 :
        const textNode = verify_code_div.firstChild;
        txt_observer.observe(textNode, { characterData: true, characterDataOldValue: true });
      }
    }

    // 处理验证码文本变化
    function handle_txt_change() {
      const verify_value = verify_code_div.textContent.trim();
      console.log('--------->   验证码变为 : ' + verify_value);
      if (verify_value && verify_value.length == VERIFY_CODE_LEN) input_verify_code('#verifyCode', verify_value);
    }

    // 初始化验证码监听
    start_txt_observing();
    handle_txt_change()

  } else {
    console.error('验证码的 div 不存在');
    setTimeout(observe_txt_verify_code, 500);
  }
}


// 使用 MutationObserver 监听验证码图片是否加载 ( 页面载入时, 验证码图片未渲染出来 )
function observe_img_verify_code() {
  /**
   * 验证码刷新时, 
   * 不是更新了 img 标签的 src 属性, 而是直接更新了 img 标签
   * 因此, 要让点击刷新验证码时, 回填新的验证码信息, 就需要监听 .verifyCode_box 的 childList 变化
   */

  // 获取验证码的 div : ( verifyCode_box )
  const verify_code_box = document.querySelector('.verifyCode_box');
  if (verify_code_box) {
    console.debug('验证码 div 找到');
    let img_observer;

    // 观察验证码 div 的子属性变化 :
    function start_img_observing() {
      if (!img_observer) {
        console.debug('创建观察验证码图片变化方法')
        img_observer = new MutationObserver((mutationsList) => {
          mutationsList.forEach(mutation => {
            if (mutation.type === 'childList') {
              mutation.addedNodes.forEach(node => {
                if (node.nodeType === Node.ELEMENT_NODE && node.tagName && node.tagName.toLowerCase() === 'img') {
                  console.log('--------->   验证码 div 新增 img 标签完成, 准备识别新验证码 ...');
                  handle_img_change();
                }
              });
            }
          });
        });

        // 开始观察目标节点（即验证码 div）的子节点变化和 img 的属性变化
        img_observer.observe(verify_code_box, { childList: true, subtree: true, attributes: true, attributeFilter: ['src'] });
      }
    }

    // 提取 img, OCR 识别 :
    function handle_img_change() {
      const src = get_verify_code_img_src();
      if (src) fetch_ocr(src);
    }

    // 初始化验证码监听 : ( 再进行一次提取与识别识别 )
    start_img_observing();
    handle_img_change()

  } else {
    console.error('验证码的 div 不存在');
    setTimeout(observe_img_verify_code, 500);
  }
}
